﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Graphics.Interfaces;

namespace RFEngine.Graphics
{
    public sealed class TexturedQuad
    {
        #region Fields

        private bool _UpdateNeeded = false;

        private float _TopTextureCoordinate = 0.0f;
        private float _BottomTextureCoordinate = 1.0f;
        private float _LeftTextureCoordinate = 0.0f;
        private float _RightTextureCoordinate = 1.0f;

        private float _AspectRatio = 1.0f;

        private Vector3 _Position = Vector3.Zero;
        private Vector2 _Scale = Vector2.One;
        private Vector3 _Rotation = Vector3.Zero;
        private Color _Color = Color.White;

        private Matrix _TranslationMatrix = Matrix.Identity;
        private Matrix _RotationMatrix = Matrix.Identity;
        private Matrix _RotationXMatrix = Matrix.Identity;
        private Matrix _RotationYMatrix = Matrix.Identity;
        private Matrix _RotationZMatrix = Matrix.Identity;
        private Matrix _ScaleMatrix = Matrix.Identity;
        private Matrix _WorldMatrix = Matrix.Identity;

        private SpriteVertex[] _VerticesForDrawing = new SpriteVertex[4];

        private Vector2 _TempVector2 = Vector2.Zero;
        private Vector3 _TempVector3 = Vector3.Zero;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets the aspect ratio of this object.
        /// </summary>
        public float AspectRatio
        {
            get { return _AspectRatio; }
            set
            {
                if (_AspectRatio != value)
                    _AspectRatio = value;

                UpdateScale();
                UpdateVertices();
            }          
        }

        /// <summary>
        /// Gets or Sets the coordinate value for the top vertices.
        /// </summary>
        public float TopTextureCoordinate
        {
            get { return _TopTextureCoordinate; }
            set { _TopTextureCoordinate = value; }
        }

        /// <summary>
        /// Gets or Sets the coordinate value for the bottom vertices.
        /// </summary>
        public float BottomTextureCoordinate
        {
            get { return _BottomTextureCoordinate; }
            set { _BottomTextureCoordinate = value; }
        }

        /// <summary>
        /// Gets or Sets the coordinate value for the left vertices.
        /// </summary>
        public float LeftTextureCoordinate
        {
            get { return _LeftTextureCoordinate; }
            set { _LeftTextureCoordinate = value; }
        }

        /// <summary>
        /// Gets or Sets the coordinate value for the right vertices.
        /// </summary>
        public float RightTextureCoordinate
        {
            get { return _RightTextureCoordinate; }
            set { _RightTextureCoordinate = value; }
        }

        /// <summary>
        /// Gets or Sets the center position for all vertices in this quad.
        /// </summary>
        public Vector3 Position
        {
            get { return _Position; }
            set
            {
                _Position = value;

                UpdatePosition();
                UpdateVertices();
            }
        }

        /// <summary>
        /// Gets the Sets the rotation of this object.
        /// </summary>
        public Vector3 Rotation
        {
            get { return _Rotation; }
            set
            {
                _Rotation = value;

                UpdateRotation();
                UpdateVertices();
            }
        }

        /// <summary>
        /// Gets or Sets the scale of this object.
        /// </summary>
        public Vector2 Scale
        {
            get { return _Scale; }
            set 
            { 
                _Scale = value;

                UpdateScale();
                UpdateVertices();
            }
        }

        /// <summary>
        /// Gets or Sets the color for all of the vertices in this quad.
        /// </summary>
        public Color Color
        {
            get { return _Color; }
        }

        /// <summary>
        /// Gets or Sets the drawing vertices of this object.
        /// </summary>
        public SpriteVertex[] VerticesForDrawing
        {
            get { return _VerticesForDrawing; }
            set { _VerticesForDrawing = value; }
        }

        #endregion

        #region Methods

        #region Constructor(s)

        /// <summary>
        /// Constructs a new instance of this object.
        /// </summary>
        public TexturedQuad() 
        {
            _VerticesForDrawing = new SpriteVertex[]
            {
                new SpriteVertex(new Vector3(-1, 1, 0), _Color, new Vector2(_LeftTextureCoordinate, _TopTextureCoordinate)),
                new SpriteVertex(new Vector3(1, 1, 0), _Color, new Vector2(_RightTextureCoordinate, _TopTextureCoordinate)),
                new SpriteVertex(new Vector3(-1, -1, 0), _Color, new Vector2(_LeftTextureCoordinate, _BottomTextureCoordinate)),                

                new SpriteVertex(new Vector3(-1, -1, 0), _Color, new Vector2(_LeftTextureCoordinate, _BottomTextureCoordinate)),
                new SpriteVertex(new Vector3(1, 1, 0), _Color, new Vector2(_RightTextureCoordinate, _TopTextureCoordinate)),
                new SpriteVertex(new Vector3(1, -1, 0), _Color, new Vector2(_RightTextureCoordinate, _BottomTextureCoordinate))                
            };
        }

        #endregion

        /// <summary>
        /// Method for setting the color of all the verticies of this quad.
        /// </summary>
        /// <param name="color"></param>
        public void SetColor(ref Color color)
        {
            for (int i = 0; i < _VerticesForDrawing.Length; i++)
                _VerticesForDrawing[i].Color = color;
        }

        /// <summary>
        /// Method for setting the position of this quad in world space.
        /// </summary>
        /// <param name="position"></param>
        public void SetPosition(ref Vector3 position)
        {
            SetPositionRotationScale(ref position, ref _Rotation, ref _Scale);
        }

        /// <summary>
        /// Method for setting the rotation of this quad in world space.
        /// </summary>
        /// <param name="rotation"></param>
        public void SetRotation(ref Vector3 rotation)
        {
            SetPositionRotationScale(ref _Position, ref rotation, ref _Scale);
        }

        /// <summary>
        /// Method for setting the scale of this quad in world space.
        /// </summary>
        /// <param name="scale"></param>
        public void SetScale(ref Vector2 scale)
        {
            SetPositionRotationScale(ref _Position, ref _Rotation, ref scale);
        }

        /// <summary>
        /// Method used for updating the texture coordinates of this quad.
        /// </summary>
        /// <param name="state">The state to use when updating the coordinates.</param>
        public void UpdateTextureCoords(TextureState state)
        {
            switch (state)
            {
                case TextureState.FlippedVertical:
                    _VerticesForDrawing[0].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[1].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[2].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[3].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[4].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[5].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    break;

                case TextureState.FlippedHorizontal:
                    _VerticesForDrawing[0].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[1].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[2].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[3].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[4].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[5].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    break;

                case TextureState.FlippedDiagonal:
                    _VerticesForDrawing[0].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[1].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[2].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[3].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[4].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[5].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    break;

                default:
                    _VerticesForDrawing[0].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[1].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[2].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[3].TextureCoordinate = GetVector2(_LeftTextureCoordinate, _BottomTextureCoordinate);
                    _VerticesForDrawing[4].TextureCoordinate = GetVector2(_RightTextureCoordinate, _TopTextureCoordinate);
                    _VerticesForDrawing[5].TextureCoordinate = GetVector2(_RightTextureCoordinate, _BottomTextureCoordinate);
                    break;
            }
        }

        /// <summary>
        /// Method for setting the position and scale of the vertices this object uses.
        /// </summary>
        /// <param name="position">The position to center the vertices around</param>
        /// <param name="rotation">The rotation to use when setting the vertice positions</param>
        /// <param name="scale">The scale to use when positioning the vertices</param>
        public void SetPositionRotationScale(ref Vector3 position, ref Vector3 rotation, ref Vector2 scale)
        {
            #region Update Rotation

            if (_Position != position)
            {
                _Position = position;
                UpdatePosition();
            }

            #endregion

            #region Update Scale

            if (_Scale != scale)
            {
                _Scale = scale;
                UpdateScale();
            }

            #endregion

            #region Update Rotation

            if (_Rotation != rotation)
            {
                _Rotation = rotation;
                UpdateRotation();
            }

            #endregion

            if (_UpdateNeeded)
                UpdateVertices();
        }

        #region Private Methods

        /// <summary>
        /// Method for forcing an update on the _TranslationMatrix.
        /// </summary>
        private void UpdatePosition()
        {
            GetVector3(_Position.X, _Position.Y, _Position.Z);
            Matrix.CreateTranslation(ref _TempVector3, out _TranslationMatrix);
            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing an update on the rotation matricies.
        /// </summary>
        private void UpdateRotation()
        {
            Matrix.CreateRotationX(MathHelper.ToRadians(_Rotation.X), out _RotationXMatrix);
            Matrix.CreateRotationY(MathHelper.ToRadians(_Rotation.Y), out _RotationYMatrix);
            Matrix.CreateRotationZ(MathHelper.ToRadians(_Rotation.Z), out _RotationZMatrix);
            _RotationMatrix = _RotationXMatrix * _RotationYMatrix * _RotationZMatrix;
            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing an update on the _ScaleMatrix.
        /// </summary>
        private void UpdateScale()
        {            
            GetVector3(_Scale.X * _AspectRatio, _Scale.Y, 1.0f);
            Matrix.CreateScale(ref _TempVector3, out _ScaleMatrix);
            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing an update on the drawing vertices.
        /// </summary>
        private void UpdateVertices()
        {
            _WorldMatrix = _ScaleMatrix * _RotationMatrix * _TranslationMatrix;

            GetVector3(-1, 1.0f, 0.0f);
            _VerticesForDrawing[0].Position = Vector3.Transform(_TempVector3, _WorldMatrix);

            GetVector3(1.0f, 1.0f, 0.0f);
            _VerticesForDrawing[1].Position = Vector3.Transform(_TempVector3, _WorldMatrix);

            GetVector3(-1.0f, -1.0f, 0.0f);
            _VerticesForDrawing[2].Position = Vector3.Transform(_TempVector3, _WorldMatrix);


            GetVector3(-1.0f, -1.0f, 0.0f);
            _VerticesForDrawing[3].Position = Vector3.Transform(_TempVector3, _WorldMatrix);

            GetVector3(1.0f, 1.0f, 0.0f);
            _VerticesForDrawing[4].Position = Vector3.Transform(_TempVector3, _WorldMatrix);

            GetVector3(1.0f, -1.0f, 0.0f);
            _VerticesForDrawing[5].Position = Vector3.Transform(_TempVector3, _WorldMatrix);

            _UpdateNeeded = false;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Method for getting a temporary vector2 value. Used for this objects Matrix calculations.
        /// </summary>
        /// <param name="x">The position along the x-axis in world space.</param>
        /// <param name="y">The position along the y-axis in world space.</param>
        protected Vector2 GetVector2(float x, float y)
        {
            _TempVector2.X = x;
            _TempVector2.Y = y;

            return _TempVector2;
        }

        /// <summary>
        /// Method for getting a temporary vector3 value. Used for this objects Matrix calculations.
        /// </summary>
        /// <param name="x">The position along the x-axis in world space.</param>
        /// <param name="y">The position along the y-axis in world space.</param>
        /// <param name="z">The position along the z-axis in world space.</param>
        protected Vector3 GetVector3(float x, float y, float z)
        {
            _TempVector3.X = x;
            _TempVector3.Y = y;
            _TempVector3.Z = z;

            return _TempVector3;
        }

        #endregion

        #endregion
    }
}
